package src.glapp;

import org.lwjgl.util.glu.*;
import src.Message;
import src.glmodel.GL_Vector;
import src.glmodel.GL_Matrix;

/**
 * Camera class by Philipp Crocoll at CodeColony (codecolony.de). Ported from
 * C++ to Java by mark napier (potatoland.org). Can move camera position
 * forward/back, pan left/right, shift up/down, and rotate around X, Y, Z axes.
 * Uses gluLookat() to orient scene to camera position. See Camera.render(). To
 * use: renderScene() { GL11.glMatrixMode(GL11.GL_MODELVIEW);
 * GL11.glLoadIdentity(); camera.Render(); // render scene ... } Requires
 * GL_Vector to perform vector operations. jul13,2006: added move(x,y,z). added
 * ctor(pos,dir,up).
 */
public class GLCamera
{
	static final float	PIdiv180	= 0.0174532925f;
	public GL_Vector	ViewDir;
	public GL_Vector	RightVector;
	public GL_Vector	UpVector;
	public GL_Vector	Position;
	public float		RotatedX, RotatedY, RotatedZ;

	public GLCamera()
	{
		setCamera(0f, 0f, 0f, // position at origin
				0f, 0f, -1f, // looking down Z axis
				0f, 1f, 0f); // camera up axis is straight up Y axis
	}

	public GLCamera(float posx, float posy, float posz, float dirx, float diry, float dirz, float upx, float upy, float upz)
	{
		setCamera(posx, posy, posz, dirx, diry, dirz, upx, upy, upz);
	}

	/**
	 * Set the camera position, view direction and up vector. NOTE: direction is
	 * direction the camera is facing NOT a target position (as in gluLookAt()).
	 * 
	 * @param posx
	 *            Position of camera
	 * @param posy
	 * @param posz
	 * @param dirx
	 *            Direction camera is facing
	 * @param diry
	 * @param dirz
	 * @param upx
	 *            Up vector
	 * @param upy
	 * @param upz
	 */
	public void setCamera(float posx, float posy, float posz, float dirx, float diry, float dirz, float upx, float upy, float upz)
	{
		if (upx == 0 && upy == 0 && upz == 0)
		{
			Message.msg("GLCamera.setCamera(): Up vector needs to be defined");
			upx = 0;
			upy = 1;
			upz = 0;
		}
		if (dirx == 0 && diry == 0 && dirz == 0)
		{
			Message.msg("GLCamera.setCamera(): ViewDirection vector needs to be defined");
			dirx = 0;
			diry = 0;
			dirz = -1;
		}
		Position = new GL_Vector(posx, posy, posz);
		ViewDir = new GL_Vector(dirx, diry, dirz);
		UpVector = new GL_Vector(upx, upy, upz);
		RightVector = GL_Vector.crossProduct(ViewDir, UpVector);
		RotatedX = RotatedY = RotatedZ = 0.0f; // TO DO: should set these to correct values
	}

	/**
	 * Set the camera to look at a target. Positions the camera on the same X
	 * and Y as the target, at the Z value specified by the distance param,
	 * looking down the Z axis.
	 * 
	 * @param targetX
	 *            camera will face this XYZ
	 * @param targetY
	 * @param targetZ
	 * @param distance
	 *            distance from target
	 */
	public void setCamera(float targetX, float targetY, float targetZ, float distance)
	{
		Position = new GL_Vector(targetX, targetY, targetZ + distance);
		ViewDir = new GL_Vector(0, 0, -1);
		UpVector = new GL_Vector(0, 1, 0);
		RightVector = GL_Vector.crossProduct(ViewDir, UpVector);
		RotatedX = RotatedY = RotatedZ = 0.0f;
	}

	/**
	 * Move camera position in the given direction
	 */
	public void viewDir(GL_Vector direction)
	{
		ViewDir = direction;
		RightVector = GL_Vector.crossProduct(ViewDir, UpVector);
	}

	/**
	 * Move camera position in the given direction
	 */
	public void Move(GL_Vector Direction)
	{
		Position = GL_Vector.add(Position, Direction);
	}

	/**
	 * Move camera position in the given direction
	 */
	public void Move(float x, float y, float z)
	{
		GL_Vector Direction = new GL_Vector(x, y, z);
		Position = GL_Vector.add(Position, Direction);
	}

	/**
	 * Move camera to the given xyz
	 */
	public void MoveTo(float x, float y, float z)
	{
		Position = new GL_Vector(x, y, z);
	}

	public void RotateX(float Angle)
	{
		RotatedX += Angle;

		//Rotate viewdir around the right vector:
		ViewDir = GL_Vector.normalize(GL_Vector.add(GL_Vector.multiply(ViewDir, (float) Math.cos(Angle * PIdiv180)), GL_Vector.multiply(UpVector, (float) Math.sin(Angle * PIdiv180))));

		//now compute the new UpVector (by cross product)
		UpVector = GL_Vector.multiply(GL_Vector.crossProduct(ViewDir, RightVector), -1);
	}

	public void RotateY(float Angle)
	{
		RotatedY += Angle;

		//Rotate viewdir around the up vector:
		ViewDir = GL_Vector.normalize(GL_Vector.sub(GL_Vector.multiply(ViewDir, (float) Math.cos(Angle * PIdiv180)), GL_Vector.multiply(RightVector, (float) Math.sin(Angle * PIdiv180))));

		//now compute the new RightVector (by cross product)
		RightVector = GL_Vector.crossProduct(ViewDir, UpVector);
	}

	public void RotateZ(float Angle)
	{
		RotatedZ += Angle;

		//Rotate viewdir around the right vector:
		RightVector = GL_Vector.normalize(GL_Vector.add(GL_Vector.multiply(RightVector, (float) Math.cos(Angle * PIdiv180)), GL_Vector.multiply(UpVector, (float) Math.sin(Angle * PIdiv180))));

		//now compute the new UpVector (by cross product)
		UpVector = GL_Vector.multiply(GL_Vector.crossProduct(ViewDir, RightVector), -1);
	}

	/**
	 * Rotate the camera around the absolute vertical axis (0,1,0), NOT around
	 * the cameras Y axis. This simulates a person looking up or down and
	 * rotating in place. You will rotate your body around the vertical axis,
	 * while your head remains tilted at the same angle.
	 * 
	 * @param Angle
	 *            the angle to rotate around the vertical axis in degrees
	 */
	public void RotateV(float Angle)
	{
		// Make a matrix to rotate the given number of degrees around Y axis
		GL_Matrix M = GL_Matrix.rotateMatrix(0, (float) Math.toRadians(Angle), 0);
		// rotate the view vector 
		GL_Vector vd = M.transform(ViewDir);
		// the up vector is perpendicular to the old view and the new view
		UpVector = (Angle > 0) ? GL_Vector.crossProduct(ViewDir, vd) : GL_Vector.crossProduct(vd, ViewDir);
		// the right vector is perpendicular to the new view and Up vectors
		RightVector = GL_Vector.crossProduct(vd, UpVector);
		// set the view direction
		ViewDir = vd;
		RotatedY += Angle;
	}

	public void MoveForward(float Distance)
	{
		Position = GL_Vector.add(Position, GL_Vector.multiply(ViewDir, -Distance));
	}

	public void MoveUpward(float Distance)
	{
		Position = GL_Vector.add(Position, GL_Vector.multiply(UpVector, Distance));
	}

	public void StrafeRight(float Distance)
	{
		Position = GL_Vector.add(Position, GL_Vector.multiply(RightVector, Distance));
	}

	/**
	 * Call GLU.gluLookAt() to set view position, direction and orientation. Be
	 * sure that the modelview matrix is current before calling Render()
	 * (glMatrixMode(GL_MODEL_VIEW)).
	 */
	public void Render()
	{
		//The point at which the camera looks:
		GL_Vector ViewPoint = GL_Vector.add(Position, ViewDir);

		//as we know the up vector, we can easily use gluLookAt:
		GLU.gluLookAt(Position.x, Position.y, Position.z, ViewPoint.x, ViewPoint.y, ViewPoint.z, UpVector.x, UpVector.y, UpVector.z);
	}

	/**
	 * Return the current camera view direction
	 */
	public GL_Vector getViewDir()
	{
		return ViewDir;
	}

}
